Vue2管理系统实现动态路由权限管理和动态侧边菜单栏

您所在的位置:网站首页 vue 左侧菜单 Vue2管理系统实现动态路由权限管理和动态侧边菜单栏

Vue2管理系统实现动态路由权限管理和动态侧边菜单栏

2023-08-07 06:21| 来源: 网络整理| 查看: 265

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

前言

最近在开发健康管理系统,管理系统一定逃不掉权限这一块,需求:需要根据不用的用户匹配不同的管理权限,既:匹配不同的操作导航,尤其体现在后台管理系统内,如果仅仅只是在导航菜单内不予显示,仍然是可以通过路径直接打开页面,因为其路由信息已经在路由信息对象(new Router({}))函数中进行了注册,项目模版vue-admin-template

目录 ├── build # 构建相关 ├── mock # 模拟数据 ├── public # 静态资源 │ │── favicon.ico # favicon图标 │ └── index.html # html模板 ├── src # 源代码 │ ├── api # 所有请求 │ ├── assets # 主题 字体等静态资源 │ ├── components # 全局公用组件 │ ├── icons # 项目所有 svg icons │ ├── layout # 全局 layout │ ├── router # 路由 │ ├── store # 全局 store管理 │ ├── styles # 全局样式 │ ├── utils # 全局公用方法 │ ├── views # views 所有页面 │ ├── A pp.vue # 入口页面 │ ├── main.js # 入口文件 加载组件 初始化等 │ └── permission.js # 权限管理 ├── tests # 测试 ├── .env.xxx # 环境变量配置 ├── .eslintrc.js # eslint 配置项 ├── .babelrc # babel-loader 配置 ├── .travis.yml # 自动化CI配置 ├── vue.config.js # vue-cli 配置 ├── postcss.config.js # postcss 配置 └── package.json # package.json 复制代码 开发

逻辑

配置两个路由数组:

一个是公共的,无需权限都可以加载,比如首页,登录页,404页面等; 一个是动态的,配置角色权限,从而动态选择是否显示;

点击登录后,会返回该用户的权限信息,拿去和动态路由数组的角色权限做配对,把该用户可以访问的路由筛选出来。

最后通过 vue 的 addRoutes方法把筛选出来的数组动态添加到实际路由对象即可。

准备工作

1.修改element-ui为中文状态

// 在src/main.js中 // Vue.use(ElementUI, { locale }) // 如果想要中文版 element-ui,按如下方式声明 Vue.use(ElementUI) 复制代码

2.删除沉余路由,只保留登录页和首页

export const constantRoutes = [ { path: '/login', component: () => import('@/views/login/index'), hidden: true }, { path: '/', component: Layout, redirect: '/dashboard', children: [{ path: 'dashboard', name: 'Dashboard', component: () => import('@/views/dashboard/index'), meta: { title: '首页', icon: 'dashboard' } }] }, ] 复制代码

3.关闭eslint校验(也可以不管,个人不太感冒)

//在vue.config.js中 module.exports = { publicPath: '/admin', outputDir: 'dist', assetsDir: 'static', // lintOnSave: process.env.NODE_ENV === 'development', lintOnSave:false, //关闭eslint校验 ...... } 复制代码

4.在api文件夹内新建index.js

引入request请求工具,将登录,用户信息,侧边栏路由表接口封装成请求方法

import request from '@/utils/request' // 登录接口 export function login(data) { return request({ url: '/userinfo/login', method: 'POST', data }) } // 用户信息接口 export function getUserInfo() { return request({ url: '/index/info', method: 'GET', }) } // 侧边栏路由表接口 export function getMoveRouter() { return request({ url: '/index/menu', method: 'GET', }) } 复制代码 反向代理 //在vue.config.js中 module.exports = { publicPath: '/admin', outputDir: 'dist', assetsDir: 'static', // lintOnSave: process.env.NODE_ENV === 'development', lintOnSave:false, //关闭eslint校验 productionSourceMap: false, devServer: { port: port, open: true, overlay: { warnings: false, errors: true }, proxy:{ //配置跨域 '/api':{ target:"https://xxx-health.net", //接口域名 changOrgin:true, pathRewrite: { '^/api': '/' } } }, // before: require('./mock/mock-server.js') // 模拟数据 }, 复制代码

修改.env.development

#just a flag ENV = 'development' # base api VUE_APP_BASE_API = '/api' 复制代码

修改vue.config.js后npm run dev重启项目

登录

1 .修改src/views/login/index.vue 中的handleLogin登录方法

handleLogin() { this.$refs.loginForm.validate((valid) => { if (valid) { this.loading = true; //开启加载动画 this.$store .dispatch("user/login", this.loginForm) //调用vuex中的login异步方法 .then(() => { this.$router.push({ path: "/" }); this.loading = false;//关闭加载动画 }) .catch(() => { this.loading = false;//关闭加载动画 }); } else { console.log("error submit!!"); return false; } }); }, 复制代码

2.修改src/store/modules/user.js 的login方法

const actions = { // user login login({ commit }, userInfo) { const { username, password,userType } = userInfo return new Promise((resolve, reject) => { login({ username: username.trim(), password: password,userType:userType }).then(response => { const { token } = response.data.userInfo commit('SET_TOKEN', token) setToken(token) resolve() }).catch(error => { reject(error) }) }) }, } 复制代码

3.修改封装的请求工具 在src/utils/request.js 中

// request interceptor service.interceptors.request.use( config => { // do something before request is sent if (store.getters.token) { // let each request carry token // ['token'] is a custom headers key // please modify it according to the actual situation config.headers['token'] = getToken() //改为后端所需要的Key值 } return config }, error => { // do something with request error console.log(error) // for debug return Promise.reject(error) } ) // response interceptor service.interceptors.response.use( response => { const res = response.data const successArr = [0,200, 201, 204,20000] //成功的状态码 // if the custom code is not in successArr, it is judged as an error. if (successArr.includes(res.code)==false) { Message({ message: res.message || 'Error', type: 'error', duration: 5 * 1000 }) ...... }, 复制代码

4.修改用户信息接口 在src/store/modules/user.js中

// get user info getInfo({ commit, state }) { return new Promise((resolve, reject) => { getUserInfo().then(response => { const { name, avatar } = response.data //从data中解构出昵称和头像 commit('SET_NAME', name) //把name 保存到vuex中 commit('SET_AVATAR', avatar) //把avatar 保存到vuex中 resolve(name) }).catch(error => { reject(error) }) }) }, 复制代码

5.修改头像路径 在src/layout/components/Navbar.vue中

//删除路径后面拼接的字符串 ...... 复制代码

到这里我们已经实现了登录以及获取用户信息,接下来请求侧边栏路由表接口来动态渲染侧边栏吧

请求动态路由渲染侧边栏

1 .配置

//在src/router 文件夹下新建两个js文件 _import_development.js 和 _import_production.js 复制代码

_import_development.js

// 开发环境导入组件 module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+ 复制代码

_import_production.js

// 生产环境导入组件 module.exports = file => () => import('@/views/' + file + '.vue') 复制代码

2 .修改permission.js

const _import = require('./router/_import_' + process.env.NODE_ENV) // 获取组件 import Layout from "@/layout"; router.beforeEach(async (to, from, next) => { ...... if (hasGetUserInfo) { next() } else { try { // 如果没有获取到vuex中到name await store.dispatch('user/getInfo') //触发获取用户信息的接口 await store.dispatch('user/getRouter') //触发获取路由表的接口 //next() } catch (error) { await store.dispatch('user/resetToken') //触发vuex中 resetToken Message.error(error || 'Has Error') //弹出异常 next(`/login?redirect=${to.path}`) //然后就执行这里 跳转到 login redirect把从哪个页面出错的 NProgress.done() } } } 复制代码

3 .在src/store/modules/user.js中新增Action函数来请求路由表接口

import { login,getMoveRouter,getUserInfo } from '@/api/index' ...... const getDefaultState = () => { return { ...... menus:'', //存放路由表 } } const mutations = { ...... SET_MENU: (state, menus) => { state.menus = menus }, }, const actions = { ....... // getRouter getRouter({ commit }) { return new Promise((resolve, reject) => { getMoveRouter().then(response => { const { permissionList:menus } = response.data //添加404 页面 menus.push({ path: "/404", component: "404", hidden: true }, { path: "*", redirect: "/404", hidden: true }) commit('SET_MENU', menus) resolve() }).catch(error => { reject(error) }) }) }, } 复制代码

4 .处理获取的动态路由 src/permission.js

获取的动态路由中components属性是字符串,例components:"Layout",但我们需要的是一个对象所以我们要转换一下

...... router.beforeEach(async (to, from, next) => { // start progress barstore NProgress.start() document.title = getPageTitle(to.meta.title) const hasToken = getToken() if (hasToken) { if (to.path === '/login') { // if is logged in, redirect to the home page next({ path: '/' }) NProgress.done() } else { const hasGetUserInfo = store.getters.name if (hasGetUserInfo) { next() } else { try { // 如果没有获取到vuex中到name await store.dispatch('user/getInfo') //触发获取用户信息的接口 await store.dispatch('user/getRouter') //触发获取路由表的接口 if (store.getters.menus.length < 1) { store.getters.menus = [] next() } const menus = filterAsyncRouter(store.getters.menus) //过滤动态路由 router.addRoutes(menus) //动态挂载路由 await store.dispatch('user/keepMenu', menus) //将过滤后的异步路由传递给vuex中的menus,做侧边栏渲染的工作 next({ ...to, replace: true }) } catch (error) { await store.dispatch('user/resetToken') //触发vuex中 resetToken Message.error(error || 'Has Error') //弹出异常 next(`/login?redirect=${to.path}`) //然后就执行这里 跳转到 login redirect把从哪个页面出错的 NProgress.done() } } } } else { /* has no token*/ if (whiteList.indexOf(to.path) !== -1) { // in the free login whitelist, go directly next() } else { // other pages that do not have permission to access are redirected to the login page. next(`/login?redirect=${to.path}`) NProgress.done() } } }) // 遍历后台传来的路由字符串,转换为组件对象 function filterAsyncRouter(asyncRouterMap) { const accessedRouters = asyncRouterMap.filter(route => { if (route.component) { if (route.component === 'Layout') { route.component = Layout //将字符串转换为对象 } else { try { var key = "icon"; var value = "form" route.meta[key] = value; route.component = _import(route.component) // 导入组件 } catch (error) { route.component = _import('system/menu/index') // 导入组件 //弹出异常('请修改或者删除不存在的组件路径') } } } if (route.children && route.children.length) { route.children = filterAsyncRouter(route.children) } return true }) return accessedRouters } 复制代码

处理之后点击登录报错 image.png 这个报错是因为vuex获取的动态路由找不到该路径的文件

vue动态路由数据库表设计

image.png

image.png

USE [CHMS] GO /****** Object: Table [dbo].[SysMenu] Script Date: 2021/5/5 23:09:37 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[SysMenu]( [Id] [int] IDENTITY(10000,1) NOT NULL, [Name] [nvarchar](32) NOT NULL, [ParentId] [int] NULL, [Path] [nvarchar](256) NOT NULL, [Weight] [int] NOT NULL, [ExtData] [text] NULL, [UpdateTime] [bigint] NOT NULL, [CreateTime] [bigint] NOT NULL, [Updator] [varchar](16) NULL, [Creator] [varchar](16) NOT NULL, [Children] [text] NULL, [Component] [nvarchar](256) NULL, [Redirect] [nvarchar](256) NULL, [Title] [nvarchar](50) NULL, [Icon] [nvarchar](50) NULL, [Hidden] [nvarchar](50) NULL, [Tag] [nvarchar](50) NULL, [Class_layer] [int] NULL, [Action_type] [nvarchar](500) NULL, [DyId] [nvarchar](50) NULL, CONSTRAINT [PK_SysMenu] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO ALTER TABLE [dbo].[SysMenu] ADD CONSTRAINT [DF_SysMenu_Weight] DEFAULT ((0)) FOR [Weight] GO ALTER TABLE [dbo].[SysMenu] ADD CONSTRAINT [DF_SysMenu_UpdateTime] DEFAULT ((0)) FOR [UpdateTime] GO ALTER TABLE [dbo].[SysMenu] ADD CONSTRAINT [DF_SysMenu_CreateTime] DEFAULT ((0)) FOR [CreateTime] GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'数据ID' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Id' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'路由名称' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Name' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'父级数据ID' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'ParentId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'路由地址' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Path' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'排序权重' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Weight' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'扩展数据' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'ExtData' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'更新时间(JS时间戳)' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'UpdateTime' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'创建时间(JS时间戳)' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'CreateTime' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'更新人用户名' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Updator' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'创建人用户名' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Creator' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'嵌套子类' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Children' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'组件地址' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Component' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'跳转' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Redirect' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'标题' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Title' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'图片' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Icon' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'是否隐藏' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Hidden' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'显示名称' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Tag' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'层级' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Class_layer' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'权限集合' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'Action_type' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'调用ID' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu', @level2type=N'COLUMN',@level2name=N'DyId' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'系统菜单' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'SysMenu' GO 复制代码

根据路由表的component列名的路径,创建对应的文件,输入账号,密码后登录报错没有了

但是侧边栏并没有动态渲染,我们需要使用vuex里的动态路由表,根据vuex中可访问的路由渲染侧边栏组件

动态渲染 //在src/layout/components/sidebar/index.vue import store from '@/store' export default { ...... computed: { routes() { //将通用路由表和动态需要根据权限加载的路由表合并,通过v-for动态渲染 return this.$router.options.routes.concat(store.getters.menus) }, ...... }, } 复制代码

效果图

admin登录

image.png

销售员登录

image.png



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3